iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Vibe Coding

從課堂半成品到完整發布:獨立遊戲的30天重生記系列 第 20 篇

【DAY20】Unity 2D 敘事遊戲開發:系統選單總成——Pause Menu 與跨場景常駐按鈕(PersistentMenuController)

  • 分享至 

  • xImage
  •  

一、前言:
隨著遊戲功能越來越豐富,玩家隨時需要一個能夠 「暫停遊戲、調整設定」 的通用介面,同時也需要一套能跨場景動態控制常駐按鈕(如選單按鈕) 顯隱的機制。今天我們要邁入遊戲架構的重要里程碑——實裝全域暫停選單總成與跨場景常駐選單管理器!我們將透過 PauseMenuController.cs 與 PersistentMenuController.cs 解決時間軸凍結(Time.timeScale)、背景對話偷跑防護,以及遊戲內常駐按鈕的場景白名單過濾。

二、 系統選單與常駐 UI 架構需求
在 2D 敘事與經營遊戲中,選單與暫停系統需要兼顧全域控制與場景適應性:

  • 時間軸凍結與全域旗標 (Time.timeScale):
    • 點擊 ESC 或暫停按鈕時將 Time.timeScale 設為 0f,凍結遊戲世界的物理與計時。
    • 提供全域靜態布林值 IsGamePaused,供其他腳本(如對話系統)作為早期返回(Early Return)的依據。
  • 遊戲內常駐按鈕動態過濾 (PersistentMenuController 與 InGameButtons):
    • 透過場景白名單(Title Scene Names),讓特定的常駐按鈕在標題畫面或特定場景自動隱藏,進入遊戲遊玩場景才自動顯示,避免在不該出現的地方被誤觸。
  • 背景對話與輸入防護:
    • 防止時間暫停後,對話管理器的 Update() 仍舊在背景接收點擊,導致解除暫停時劇情自動狂飆。

三、 核心程式實作:PauseMenuController.cs 與 PersistentMenuController 協作架構
為了讓常駐物件完美運作,我們在 DontDestroyOnLoad 下的同一個管理物件中同時掛載了兩個核心腳本:負責管理常駐按鈕顯隱的 PersistentMenuController,以及負責暫停與時間軸控制的 PauseMenuController。

1. PauseMenuController.cs(暫停與時間凍結核心):

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PauseMenuController : MonoBehaviour {
    [Header("UI 連結設定")]
    public GameObject pauseMenuPanel; // 暫停選單面板
    public Button pauseButton;        // 畫面上的暫停按鈕
    public Button resumeButton;       // 繼續遊戲按鈕
    public Button mainMenuButton;     // 返回主選單按鈕

    // 全域靜態變數,供其他腳本(如對話系統)檢查遊戲是否暫停
    public static bool IsGamePaused = false;
    void Start()  {
        if (pauseMenuPanel != null)
            pauseMenuPanel.SetActive(false); // 遊戲開始時預設隱藏
        if (pauseButton != null)
            pauseButton.onClick.AddListener(TogglePause);
        if (resumeButton != null)
            resumeButton.onClick.AddListener(ResumeGame);
        if (mainMenuButton != null)
            mainMenuButton.onClick.AddListener(GoToMainMenu);
    }
    void Update() {
        DetectEscapeKey();
    }

    private void DetectEscapeKey() {
#if ENABLE_INPUT_SYSTEM
        if (UnityEngine.InputSystem.Keyboard.current != null &&
            UnityEngine.InputSystem.Keyboard.current.escapeKey.wasPressedThisFrame) {
            TogglePause();
        }
#else
        if (Input.GetKeyDown(KeyCode.Escape)) {
            TogglePause();
        }
#endif
    }
    public void TogglePause() {
        if (IsGamePaused) ResumeGame();
        else PauseGame();
    }
    public void PauseGame()  {
        if (pauseMenuPanel != null) pauseMenuPanel.SetActive(true);
        Time.timeScale = 0f; // 凍結時間軸
        IsGamePaused = true;
        Debug.Log("遊戲已暫停");
    }
    public void ResumeGame() {
        if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
        Time.timeScale = 1f; // 恢復時間軸
        IsGamePaused = false;
        Debug.Log("遊戲已恢復");
    }
    public void GoToMainMenu() {
        // **關鍵防呆**:離開場景前務必將時間軸恢復,否則下個場景會直接卡死!
        Time.timeScale = 1f;
        IsGamePaused = false;
        SceneManager.LoadScene("SampleScene"); // 替換為你的主選單場景名稱
    }
}

2. PersistentMenuController.cs(負責管理常駐按鈕顯隱):

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class PersistentMenuController : MonoBehaviour {
    [Header("UI 容器設定")]
    public GameObject inGameButtonsContainer; // 遊戲中的常駐按鈕容器 (例如暫停、存檔按鈕)

    [Header("黑名單設定 (不顯示常駐按鈕的場景)")]
    public List<string> hideInScenes = new List<string> { "TitleScene", "MainMenuScene" };

    void OnEnable() {
        // 註冊場景切換事件
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnDisable() {
        // 取消註冊場景切換事件,避免記憶體洩漏
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode) {
        CheckButtonVisibility(scene.name);
    }

    private void CheckButtonVisibility(string sceneName) {
        if (inGameButtonsContainer == null) return;

        // 如果當前場景在黑名單內,就隱藏常駐按鈕;否則顯示
        bool shouldHide = hideInScenes.Contains(sceneName);
        inGameButtonsContainer.SetActive(!shouldHide);
        
        // 離開某些特定場景時,順便確保時間軸是正常運行的(防止卡死)
        if (!shouldHide && Time.timeScale == 0f) {
            Time.timeScale = 1f;
        }
    }
}
  • PersistentMenuController 與 InGameButtons 的動態顯隱機制: 透過宣告場景名稱清單(Title Scene Names),當玩家切換到標題或特定的選單場景時,InGameButtons 容器會自動隱藏;進入遊戲關卡時則自動展開,確保 UI 介面在不同遊戲階段保持乾淨與正確。

四、 Unity Inspector 設定與實測驗收
在 Unity 介面中將元件進行配置與串接:
1. 常駐總成物件配置 (PersistentManagers)

  • 在 DontDestroyOnLoad 物件下掛載 PersistentMenuController 與 PauseMenuController。
  • 將遊戲內常駐按鈕群組指定給 InGameButtons,並在 Title Scene Names 填入標題場景名稱(如 SampleScene)。
  • 將 PauseMenuPanel 與對應的按鈕(繼續遊戲、返回主選單)拖曳至 PauseMenuController 的 Inspector 欄位中。
    https://ithelp.ithome.com.tw/upload/images/20260924/20184073MrFmYFLeMP.png
    圖一、配置與串接示意圖
  1. 暫停與常駐按鈕實測
  • 當玩家處於 SampleScene 標題畫面時,InGameButtons 自動隱藏,不會干擾主選單畫面。
  • 進入遊戲場景後,常駐按鈕順利顯示。點擊按鈕或按下 Escape 鍵,PauseMenuPanel 彈出且背景時間軸完美凍結(Time.timeScale = 0f)。
  • 點擊「返回主選單」後,時間軸成功重設為 1f,畫面流暢切回標題場景。
    https://ithelp.ithome.com.tw/upload/images/20260924/20184073FQXqyOZlWE.png
    圖二、主畫面沒有顯現暫停鍵

https://ithelp.ithome.com.tw/upload/images/20260924/20184073YDwJUA27dQ.png
*圖三、到下一幕點擊Setting即顯現暫停鍵8

五、 開發實戰經驗:UI 防錯與時間軸設計
1. Time.timeScale = 0f 的背景防禦(Early Return): 時間暫停雖然能凍結物理和動畫,但無法自動阻止寫在 Update() 裡的點擊事件。記得在對話管理器或重要點擊腳本中加入檢查,這樣才能徹底杜絕暫停時背景程式偷偷運作的隱患。

if (PauseMenuController.IsGamePaused) return;

2. 場景切換的時間軸還原機制: 在任何會呼叫 SceneManager.LoadScene() 的按鈕或轉場邏輯中,第一行絕對要寫 Time.timeScale = 1f。這是無數開發者在實作暫停功能時最容易踩的經典大坑。

3. 善用 PersistentMenuController 管理非同步場景 UI: 將遊戲內常用按鈕包裝成 InGameButtons 並交由白名單過濾,能大幅減少各個場景需要手動開關 UI 的重複程式碼。

六、 今日成果與小結

  • 時間軸凍結控制:實裝了 Time.timeScale 的 0 與 1 切換,並妥善處理轉場前的重設防呆。
  • 常駐按鈕動態過濾:透過 PersistentMenuController 與 InGameButtons 成功實現跨場景 UI 顯隱自動化。
  • 全域狀態防護:透過 IsGamePaused 旗標,成功攔截暫停期間背景對話與按鈕的誤觸!

範例影片:Yes

最後,別忘了上傳紀錄!DAY 20 的內容是實作 PauseMenuController(暫停與時間軸凍結核心) 與 PersistentMenuController(跨場景常駐按鈕動態顯隱機制),並且完美收尾了整個暫停選單與常駐 UI 的協作架構。因為這依舊是延續前幾天(Day 19)建立的常駐管理器與 UI 總成架構,所以它完全屬於同一個獨立分支 feature/persistent-managers:

1.檢查修改狀態
git status
2.加入暫存區並提交 Commit
git add .
git commit -m "feat: 實作 PauseMenuController 暫停與時間軸凍結機制,並結合 PersistentMenuController 完成常駐按鈕場景白名單過濾"
3.推送到遠端開發分支
git push origin feature/persistent-managers


上一篇
【DAY19】Unity 2D 敘事遊戲開發:架構大躍進——跨場景常駐與 PersistentCanvas 的建立
下一篇
【DAY21】Unity 2D 敘事遊戲開發:主選單返回機制與時間軸防呆重設(GoToMainMenu)
系列文
從課堂半成品到完整發布:獨立遊戲的30天重生記 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言